﻿//Copyright (C) Riwut Libinuko, 2010 - Ideas For Free
//http://blog.libinuko.com

// Combination technique in 
// http://www.yoda.arachsys.com/csharp/singleton.html
// http://stefanoricciardi.net/2009/10/13/service-locator-pattern-in-c-with-lazy-initialization/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Microsoft.SharePoint;
using SharePointZip.Tools;
using SharePointZip.Controller;
using SharePointZip.Controls;
using ICSharpCode.SharpZipLib.Zip;

namespace SharePointZip.Base
{
    /// <summary>
    /// Basic implementation of <see cref="IServiceLocator"></see>
    /// </summary>
    public sealed class ServiceLocator : IServiceLocator
    {
        #region Properties and Field
        private IDictionary<Type, Type> serviceType;
        private static readonly object objLock = new Object();
        private IDictionary<Type, object> instantiatedServices;

        /// <summary>
        /// Get the <see cref="IServiceLocator"/> instance.
        /// </summary>
        public static IServiceLocator Instance
        {
            get
            {
                return ServiceLocatorSingleton.instance;
            }
        }
        #endregion

        #region ctor
        ServiceLocator()
        {
            this.serviceType = new Dictionary<Type, Type>();
            this.instantiatedServices = new Dictionary<Type, object>();

            this.BuildServiceTypesMap();
        }
        #endregion

        #region Singleton
        /// <summary>
        /// Singleton implementation
        /// </summary>
        class ServiceLocatorSingleton   
        {
            static ServiceLocatorSingleton()
            { }

            internal static readonly IServiceLocator instance = new ServiceLocator();
        }
        #endregion

        /// <summary>
        /// BuildServiceTypesMap , contains dictionary of available services.
        /// If you want to change any service implementation, please update this part.
        /// You can use external mapping to replace BuildServiceTypeMap.
        /// </summary>
        private void BuildServiceTypesMap()
        {
            serviceType.Add(typeof(ICompressorEngine<ZipOutputStream>), typeof(SharpZipLibDeflater));
            serviceType.Add(typeof(ICompress<ZipOutputStream,SPFile>), typeof(SPFileCompressor));
            serviceType.Add(typeof(ICompress<ZipOutputStream, SPFileCollection>), typeof(SPFileCollectionCompressor));
            serviceType.Add(typeof(ICompress<ZipOutputStream, SPFolder>), typeof(SPFolderCompressor));
            serviceType.Add(typeof(ICompress<ZipOutputStream,SPListItemCollection>), typeof(SPListItemCollectionCompressor));
            serviceType.Add(typeof(IUserControl<Downloader>), typeof(Downloader));
            serviceType.Add(typeof(IUserControl<Uploader>), typeof(Uploader));
        }


        #region IServiceLocator member
        /// <summary>
        /// Return service object of TInterface
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <returns>Service object</returns>
        public TInterface GetService<TInterface>()
        {
            return GetService<TInterface>(null); 
        }

        /// <summary>
        /// Return parameterized service object of TInterface
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="parameters">Contains array list of contructor parameters.</param>
        /// <returns>Service object</returns>
        public TInterface GetService<TInterface>(params object[] parameters)
        {
            if (instantiatedServices.ContainsKey(typeof(TInterface)))
            {
                return (TInterface)instantiatedServices[typeof(TInterface)];
            }
            else
            {
                TInterface service = GetNewServiceInstance<TInterface>(parameters);

                // add the service to the ones that we have already instantiated
                instantiatedServices.Add(typeof(TInterface), service);

                return service;
            }
        }

        /// <summary>
        /// Return NEW service object of TInterface
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <returns>Service object</returns>
        public TInterface GetNewServiceInstance<TInterface>()
        {
            return GetNewServiceInstance<TInterface>(null);
        }

        /// <summary>
        /// Return NEW parameterized service object of TInterface
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="parameters">Contains array list of contructor parameters</param>
        /// <returns>Service object</returns>
        public TInterface GetNewServiceInstance<TInterface>(params object[] parameters)
        {
            Type svcType;

            try
            {
                svcType = serviceType[typeof(TInterface)];
            }
            catch (KeyNotFoundException ex)
            {
                throw new ArgumentException(
                    "The type has not been registered: " + typeof(TInterface).FullName,
                    ex);
            }

            ConstructorInfo constructor;

            if (parameters == null || parameters.Length == 0)
            {
                // use reflection to invoke the service
                constructor = svcType.GetConstructor(new Type[0]);

                if (constructor == null)
                    throw new ArgumentException(
                        "Cannot find a constructor that takes no parameters for type: "
                        + typeof(TInterface).FullName);
            }
            else
            {
                constructor = svcType.GetConstructor(
                    parameters.Select(x => x.GetType()).ToArray());

                if (constructor == null)
                    throw new ArgumentException(
                        "Cannot find a constructor with the specified parameters for type: "
                        + typeof(TInterface).FullName);
            }

            return (TInterface)constructor.Invoke(parameters);
        }

        #endregion
    }
}
